package cn.xeblog.commons.util;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Class类工具类--主要用于扫描
 *
 * @author anlingyi
 * @date 2021/10/3 3:05 下午
 */
public class ClassUtils {
    /**
     * 扫描
     *
     * @param path 指定路径
     * @return 扫描出的Class对象集合
     */
    public static Set<Class<?>> scan(String path) {
        return scan(path, null);
    }

    /**
     * 扫描
     *
     * @param path 指定路径
     * @param pkg  指定的包路径
     * @return 扫描出的Class对象集合
     */
    public static Set<Class<?>> scan(String path, String pkg) {
        return scan(path, pkg, null);
    }

    /**
     * 扫描
     *
     * @param path            指定路径
     * @param pkg             指定的包路径
     * @param annotationClass 注解类class对象
     * @return 扫描出的Class对象集合
     */
    public static Set<Class<?>> scan(String path, String pkg, Class<? extends Annotation> annotationClass) {
        return scan(path, pkg, annotationClass, null);
    }

    /**
     * 扫描
     *
     * @param path       指定路径
     * @param pkg        指定的包路径
     * @param superClass 父类
     * @return 扫描出的Class对象集合
     */
    public static Set<Class<?>> scanSubClass(String path, String pkg, Class superClass) {
        return scan(path, pkg, null, superClass);
    }

    /**
     * 扫描
     * <p>1.通过注解扫描获取相关的class集合</p>
     * <p>2.通过jar包地址获取相关的class集合，扫描指定jar包前需要将这个jar的地址加入了系统类加载器的扫描列表中</p>
     * <note>注意，这里只支持单个jar的加载，如果这个jar还引入了其他jar依赖，会加载失败,所以只能用来加载对其他jar包没有依赖的简单对象类信息</note>
     * </p>
     *
     * @param path            指定路径
     * @param pkg             指定的包路径
     * @param annotationClass 注解类class对象
     * @param superClass      父类
     * @return 扫描出的Class对象集合
     *
     *
     */
    public static Set<Class<?>> scan(String path, String pkg, Class<? extends Annotation> annotationClass, Class superClass) {
        // 因为类名存在重复的可能，所以这里用的是Set
        Set<Class<?>> classes = new HashSet<>();
        String pkgPath = null;
        if (StrUtil.isNotBlank(pkg)) {
            // 将”.“替换成“/”，即换成路径格式
            pkgPath = pkg.replace(".", "/");
        }

        try {
            if (path == null) {
                classes = ClassUtil.scanPackage(pkg, (clazz) -> {
                    // 指定类不存在指定类类型的注解，返回false
                    if (!isAnnotationPresent(clazz, annotationClass)) {
                        return false;
                    }
                    // 指定类不是超类的子类或者时，返回false
                    if (!isSubClass(clazz, superClass)) {
                        return false;
                    }
                    return true;
                });
            } else {
                // 工厂类初始化之后、插件第一次加载、第一次开启游戏、服务端第一次处理客户端action会触发
                JarFile jar = new JarFile(path);
                // JarFile.entries()可以得到一个jar文件内部元素的迭代器（Enumeration）。同过这个迭代器我们可以获得所有的JarEntry
                Enumeration<JarEntry> entryEnumeration = jar.entries();
                // 存在元素时处理
                while (entryEnumeration.hasMoreElements()) {
                    JarEntry entry = entryEnumeration.nextElement();
                    // 先获取类的名称，符合条件之后再做处理，避免处理不符合条件的类
                    String clazzName = entry.getName();
                    if (pkgPath != null && !clazzName.startsWith(pkgPath)) {
                        continue;
                    }

                    if (clazzName.endsWith(".class")) {
                        // 去掉文件名的后缀
                        clazzName = clazzName.substring(0, clazzName.length() - 6);
                        // 替换分隔符
                        clazzName = clazzName.replace("/", ".");
                        // 加载类,如果失败直接跳过
                        try {
                            Class<?> clazz = Class.forName(clazzName);
                            // 指定类不存在指定类类型的注解，跳过
                            if (!isAnnotationPresent(clazz, annotationClass)) {
                                continue;
                            }
                            // 指定类不是超类的子类或者时，跳过
                            if (!isSubClass(clazz, superClass)) {
                                continue;
                            }
                            // 将类Class对象作为值存入set
                            classes.add(clazz);
                        } catch (Throwable e) {
                            // 这里可能出现有些类是依赖不全的，直接跳过，不做处理，也没法做处理
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return classes;
    }

    /**
     * 判断class对象或者注解class对象是否存在
     *
     * @param clazz           class对象
     * @param annotationClass 注解class对象
     * @return 指定类存在指定类类型的注解，返回true,否则返回false
     */
    public static boolean isAnnotationPresent(Class clazz, Class<? extends Annotation> annotationClass) {
        // 这里的作用只是为了跳过上面的false,为空不判断,直接跳过
        if (clazz == null || annotationClass == null) {
            return true;
        }
        // 指定类型的注解存在于此元素上,返回true
        return clazz.isAnnotationPresent(annotationClass);
    }


    /**
     * 判断超类是否为clazz的父类或接口或者超类和clazz是否为同一个类或同一个接口
     *
     * @param clazz      指定的比较类
     * @param superClass 超类
     * @return 超类为clazz的父类或接口或者超类和clazz为同一个类或同一个接口，返回true,否则返回false
     */
    public static boolean isSubClass(Class clazz, Class superClass) {
        // 这里的作用只是为了跳过上面的false,为空不判断,直接跳过
        if (clazz == null || superClass == null) {
            return true;
        }
        // superClass是clazz的父类或接口或者superClass和clazz为同一个类或同一个接口时，且不是同一个类
        return superClass.isAssignableFrom(clazz) && !superClass.equals(clazz);
    }

}
